package e3dds

import (
	"reflect"
)

func RegisterProtoHandler[T any](arg any, mp map[string]T) {
	var zeroT T
	rawType := reflect.TypeOf(zeroT)

	if rawType == nil || rawType.Kind() != reflect.Func {
		return
	}

	val := reflect.ValueOf(arg)
	typ := val.Type()
	if typ.Kind() == reflect.Func {
		funcName := getFunctionName(val)
		if _, ok := mp[funcName]; ok {
			panic("duplicate register proto handler for " + funcName)
		}
		if typ.ConvertibleTo(rawType) {
			converted := val.Convert(rawType)
			mp[funcName] = converted.Interface().(T)
		}
		return
	}

	if typ.Kind() != reflect.Struct && (typ.Kind() != reflect.Ptr || typ.Elem().Kind() != reflect.Struct) {
		return
	}

loopFunc:
	for i := 0; i < typ.NumMethod(); i++ {
		method := typ.Method(i)
		mt := method.Type

		if mt.NumIn() != rawType.NumIn()+1 || mt.NumOut() != rawType.NumOut() {
			continue
		}

		for j := 0; j < rawType.NumOut(); j++ {
			if mt.Out(j) != rawType.Out(j) {
				continue loopFunc
			}
		}

		for j := 0; j < rawType.NumIn(); j++ {
			if mt.In(j+1) != rawType.In(j) {
				continue loopFunc
			}
		}

		methodVal := val.Method(method.Index)
		if methodVal.Type().ConvertibleTo(rawType) {
			if _, ok := mp[method.Name]; ok {
				panic("duplicate register proto handler for " + method.Name)
			}
			converted := methodVal.Convert(rawType)
			mp[method.Name] = converted.Interface().(T)
		}
	}
}

func RegisterProtoHandlerWithName[T any](funcName string, arg T, mp map[string]T) {
	if _, ok := mp[funcName]; ok {
		panic("duplicate register proto handler for " + funcName)
	}
	mp[funcName] = arg
}
